//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
//==========================================================================
// Copyright (c) 2000-2004,  Elastos, Inc.  All Rights Reserved.
//**author:hanxu
//**y/m/d:2005/5/16
//==========================================================================

#include <ddk.h>

#include "FW82371.h"
#include "init.h"
#include "register.h"

static unsigned short s_uImask = 0xffff;

struct FW82371IsrDescriptors FW82371IRQ[SB_IRQS];

#define outb_p(val,port)\
do {\
*(volatile unsigned char *)(0xbfd00000 + (port)) = (val);  \
DzDelay(5); \
} while(0)

void FW82371DispatchIsr(irq_t cIrq, void *pvDevice, InterruptContext *pContext)
{
    unsigned char ucIRR_master;
    unsigned char ucIRR_slave;
    bool realIRQ = false;
    int master_irq,slave_irq;

// outb(0x14, 0x3f2); //clear 3f2   (something about floppy controler)?????

    ucIRR_master = inb(0x20);
    ucIRR_slave = inb(0xa0);

    for (master_irq = 0; master_irq < 8; master_irq++) {
        if (ucIRR_master &(1 << master_irq)) {
            if ((master_irq < 2) && (NULL != FW82371IRQ[master_irq].Isr)) {
                DisableFW82371Irq(master_irq);

                FW82371IRQ[master_irq].Isr(master_irq,
                    FW82371IRQ[master_irq].pDevice, pContext);

                outb(0x60 + master_irq,0x20); /* 'Specific EOI' to master */

                EnableFW82371Irq(master_irq);

                realIRQ = true;
                break;
            }
            else if (master_irq == 2) {
                for (slave_irq = 0; slave_irq < 8; slave_irq++) {
                    if ((ucIRR_slave &(1 << slave_irq)) &&
                        (NULL != FW82371IRQ[slave_irq + 8].Isr)) {

                        DisableFW82371Irq(2);
                        DisableFW82371Irq(slave_irq+8);

                        FW82371IRQ[slave_irq+8].Isr(slave_irq+8,
                            FW82371IRQ[slave_irq+8].pDevice, pContext);

                        /* 'Specific EOI' to slave */
                        outb(0x60+(slave_irq&7),0xA0);
                        outb(0x62,0x20); /* 'Specific EOI' to master-IRQ2 */

                        EnableFW82371Irq(2);
                        EnableFW82371Irq(slave_irq+8);

                        realIRQ = true;
                        break;
                    }
                }
            }
            else if ((master_irq > 2)
                    && (NULL != FW82371IRQ[master_irq].Isr)) {
                DisableFW82371Irq(master_irq);

                FW82371IRQ[master_irq].Isr(master_irq,
                    FW82371IRQ[master_irq].pDevice, pContext);

                outb(0x60 + master_irq,0x20); /* 'Specific EOI' to master */

                EnableFW82371Irq(master_irq);

                realIRQ = true;
                break;
            }
        }
    }

// if(!realIRQ)
//  kprintf("no 8259 irq return! \n");

    return;
}

EXTERN IDeviceDriver * CDECL InitFW82371IRQ(uint_t uDeviceNo, void *pvParameter)
{
    kprintf("InitFW82371IRQ......\n");

    outb(0xff, 0x21); //mask all of master 8259A irq
    outb(0xff, 0xA1); // mask all of slave 8259A irq

    // outb_p add a delay slot to outb to secure operation commands valid 
    // for 8259A
    //set master  8259A ICWx
    outb_p(0x11, 0x20); // ICW1: select master 8259A edged trigger
                        // mode/send ICW4/has slave chip mode
    outb_p(0x00, 0x21); // ICW2: master 8259A IR0-7 mapped to 0x00-0x07
    outb_p(0x04, 0x21); //ICW3:select master 8259A IRQ2 to receive slave
                        // 8259A irq signal
    outb_p(0x01, 0x21); //ICW4: master 8259A use normal EOI

    //set slave 8259A ICWx
    outb_p(0x11, 0xA0);
    outb_p(0x08, 0xA1); // ICW2: slave 8259A IR0-7 mapped to 0x08-0x0f
    outb_p(0x02, 0xA1);
    outb_p(0x01, 0xA1);

    //set south bridge FW82371 irq0~irq15 to edge trigger mode
    outb(0x0, 0x4d0);
    outb(0x0, 0x4d1);

    DzDelay(100);  // wait for 8259A to initialize

    DzRegisterIsr(3, IPL2, &FW82371DispatchIsr, NULL);

    return NULL;
}

void DisableFW82371Irq(unsigned int irq_nr)
{
    unsigned char ucMask;

    if ((irq_nr < 0) || (irq_nr > 15)) {
        kprintf("invalide FW82371 IRQ NO. input \n");
        return;
    }

    s_uImask |= 1<<irq_nr;

    if (irq_nr < 8) //master 8259A
    {
        ucMask = s_uImask << 8;
        outb(ucMask, 0x21);
    }
    else //slave 8259A
    {
        s_uImask |= 1<<2;  //disable master 8259A IRQ2
        ucMask = s_uImask << 8;
        outb(ucMask, 0x21);

        ucMask = s_uImask >>8;
        outb(ucMask, 0xa1);
    }
}

void EnableFW82371Irq(unsigned int irq_nr)
{
    unsigned char ucMask;

    if ((irq_nr < 0) || (irq_nr > 15)) {
        kprintf("invalide FW82371 IRQ NO. input \n");
        return;
    }

    s_uImask &= ~((unsigned short)(1<<irq_nr));

    if (irq_nr < 8) //master 8259A
    {
        ucMask = s_uImask << 8;
        outb(ucMask, 0x21);
    }
    else //slave 8259A
    {
        s_uImask &= ~((unsigned short)(1<<2));    //enable master 8259A IRQ2
        ucMask = s_uImask << 8;
        outb(ucMask, 0x21);

        ucMask = s_uImask >>8;
        outb(ucMask, 0xa1);
    }
}

void AddFW82371Device(unsigned int uIrq, isr_t Isr, void *pDevice)
{
    uint32_t uFlags;

    assert((uIrq >= 0)&&(uIrq < 16));
    assert(NULL != pDevice);
    assert(NULL == FW82371IRQ[uIrq].Isr);

    FW82371IRQ[uIrq].Isr = Isr;
    FW82371IRQ[uIrq].pDevice = pDevice;

    uFlags = SaveFlagsAndCli();
    EnableFW82371Irq(uIrq);       // enable bonito sub-device irq
    RestoreIF(uFlags);
}
